
// import oimo.collision.narrowphase.*;
// import oimo.collision.narrowphase.detector.*;
// import oimo.common.Setting;
// import oimo.dynamics.callback.ContactCallback;
// import oimo.dynamics.constraint.contact.*;
// import oimo.dynamics.rigidbody.*;
// import oimo.m.M;

import { Shape } from "./rigidbody/Shape";
import { M } from "../common/M";
import { ContactCallback } from "./callback/ContactCallback";
import { Setting } from "../common/Setting";
import { RigidBody } from "./rigidbody/RigidBody";
import { ContactLink } from "./ContactLink";
import { Detector } from "../collision/narrowphase/detector/Detector";
import { CachedDetectorData } from "../collision/narrowphase/detector/CachedDetectorData";
import { DetectorResult } from "../collision/narrowphase/DetectorResult";
import { Manifold } from "./constraint/contact/Manifold";
import { ManifoldUpdater } from "./constraint/contact/ManifoldUpdater";
import { ContactConstraint } from "./constraint/contact/ContactConstraint";

/**
 * A contact is a cached pair of overlapping shapes in the physics world. contacts
 * are created by `ContactManager` when two AABBs of shapes begin overlapping.
 *
 * As AABBs are larger than its shapes, shapes of a contact don't always
 * touching or colliding though their AABBs are overlapping.
 */
export class Contact {
	_next: Contact;
	_prev: Contact;

	_link1: ContactLink;
	_link2: ContactLink;

	_s1: Shape;
	_s2: Shape;
	_b1: RigidBody;
	_b2: RigidBody;

	// detector data
	_detector: Detector;
	_cachedDetectorData: CachedDetectorData;
	_detectorResult: DetectorResult;

	// tmp data
	_latest: boolean;
	_shouldBeSkipped: boolean;

	// constraint/manifold data
	_manifold: Manifold;
	_updater: ManifoldUpdater;
	_contactConstraint: ContactConstraint;
	_touching: boolean;


	constructor() {
		this._next = null;
		this._prev = null;

		this._link1 = new ContactLink();
		this._link2 = new ContactLink();

		this._s1 = null;
		this._s2 = null;
		this._b1 = null;
		this._b2 = null;

		this._detector = null;
		this._cachedDetectorData = new CachedDetectorData();
		this._detectorResult = new DetectorResult();

		this._latest = false;
		this._shouldBeSkipped = false;

		this._manifold = new Manifold();
		this._updater = new ManifoldUpdater(this._manifold);
		this._contactConstraint = new ContactConstraint(this._manifold);
		this._touching = false;
	}

	// --- ---


	private attachLinks(): void {
		if (this._b1._contactLinkList == null) {
			this._b1._contactLinkList = this._link1;
			this._b1._contactLinkListLast = this._link1;
		} else {
			this._b1._contactLinkListLast._next = this._link1;
			this._link1._prev = this._b1._contactLinkListLast._next;
			this._b1._contactLinkListLast._next = this._link1;
		}

		if (this._b2._contactLinkList == null) {
			this._b2._contactLinkList = this._link2;
			this._b2._contactLinkListLast = this._link2;
		} else {
			this._b2._contactLinkListLast._next = this._link1;
			this._link2._prev = this._b2._contactLinkListLast._next;
			this._b2._contactLinkListLast._next = this._link2;
		}
		this._b1._numContactLinks++;
		this._b2._numContactLinks++;
		this._link1._other = this._b2;
		this._link2._other = this._b1;
		this._link1._contact = this;
		this._link2._contact = this;
	}


	private detachLinks(): void {

		{
			let prev = this._link1._prev;
			let next = this._link1._next;
			if (prev != null) {
				prev._next = next;
			}
			if (next != null) {
				next._prev = prev;
			}
			if (this._link1 == this._b1._contactLinkList) {
				this._b1._contactLinkList = next;
			}
			if (this._link1 == this._b1._contactLinkListLast) {
				this._b1._contactLinkListLast = prev;
			}
			this._link1._prev = null;
			this._link1._next = null;
		}

		{
			let prev = this._link2._prev;
			let next = this._link2._next;
			if (prev != null) {
				prev._next = next;
			}
			if (next != null) {
				next._prev = prev;
			}
			if (this._link2 == this._b2._contactLinkList) {
				this._b2._contactLinkList = next;
			}
			if (this._link2 == this._b2._contactLinkListLast) {
				this._b2._contactLinkListLast = prev;
			}
			this._link2._prev = null;
			this._link2._next = null;
		}

		this._b1._numContactLinks--;
		this._b2._numContactLinks--;
		this._link1._other = null;
		this._link2._other = null;
		this._link1._contact = null;
		this._link2._contact = null;
	}


	private sendBeginContact(): void {
		var cc1: ContactCallback = this._s1._contactCallback;
		var cc2: ContactCallback = this._s2._contactCallback;
		if (cc1 == cc2) {
			cc2 = null; // avoid calling twice
		}
		if (cc1 != null) cc1.beginContact(this);
		if (cc2 != null) cc2.beginContact(this);
	}


	private sendEndContact(): void {
		var cc1: ContactCallback = this._s1._contactCallback;
		var cc2: ContactCallback = this._s2._contactCallback;
		if (cc1 == cc2) {
			cc2 = null; // avoid calling twice
		}
		if (cc1 != null) cc1.endContact(this);
		if (cc2 != null) cc2.endContact(this);
	}


	private sendPreSolve(): void {
		var cc1: ContactCallback = this._s1._contactCallback;
		var cc2: ContactCallback = this._s2._contactCallback;
		if (cc1 == cc2) {
			cc2 = null; // avoid calling twice
		}
		if (cc1 != null) cc1.preSolve(this);
		if (cc2 != null) cc2.preSolve(this);
	}


	private sendPostSolve(): void {
		var cc1: ContactCallback = this._s1._contactCallback;
		var cc2: ContactCallback = this._s2._contactCallback;
		if (cc1 == cc2) {
			cc2 = null; // avoid calling twice
		}
		if (cc1 != null) cc1.postSolve(this);
		if (cc2 != null) cc2.postSolve(this);
	}

	// --- internal ---


	public _attach(s1: Shape, s2: Shape, detector: Detector): void {
		this._s1 = s1;
		this._s2 = s2;
		this._b1 = s1._rigidBody;
		this._b2 = s2._rigidBody;
		this._touching = false;
		this.attachLinks();

		this._detector = detector;

		this._contactConstraint._attach(s1, s2);
	}


	public _detach(): void {
		if (this._touching) {
			// touching in the last frame
			this.sendEndContact();
		}

		this.detachLinks();
		this._s1 = null;
		this._s2 = null;
		this._b1 = null;
		this._b2 = null;
		this._touching = false;

		this._cachedDetectorData._clear();
		this._manifold._clear();

		this._detector = null;

		this._contactConstraint._detach();
	}

	public _updateManifold(): void {
		if (this._detector == null) return;

		var ptouching: boolean = this._touching;

		var result: DetectorResult = this._detectorResult;
		this._detector.detect(result, this._s1._geom, this._s2._geom, this._s1._transform, this._s2._transform, this._cachedDetectorData);

		var num: number = result.numPoints;
		this._touching = num > 0;

		if (this._touching) {
			// update manifold basis
			this._manifold._buildBasis(result.normal);

			// determine position correction algorithm
			if (result.getMaxDepth() > Setting.contactUseAlternativePositionCorrectionAlgorithmDepthThreshold) {
				// use alternative position correction method (split impulse by default) for deeply overlapped contacts
				this._contactConstraint._positionCorrectionAlgorithm = Setting.alternativeContactPositionCorrectionAlgorithm;
			} else {
				// use default position correction algorithm for slightly overlapped contacts
				this._contactConstraint._positionCorrectionAlgorithm = Setting.defaultContactPositionCorrectionAlgorithm;
			}

			// update contact manifold
			if (result.incremental) {
				// incremental manifold
				this._updater.incrementalUpdate(result, this._b1._transform, this._b2._transform);
			} else {
				// one-shot manifold
				this._updater.totalUpdate(result, this._b1._transform, this._b2._transform);
			}
		} else {
			this._manifold._clear();
		}

		if (this._touching && !ptouching) {
			this.sendBeginContact();
		}
		if (!this._touching && ptouching) {
			this.sendEndContact();
		}
		if (this._touching) {
			this.sendPreSolve();
		}
	}

	// called from the contact manager
	public _postSolve(): void {
		this.sendPostSolve();
	}

	// --- public ---

	/**
	 * Returns the first shape of the contact.
	 */
	public getShape1(): Shape {
		return this._s1;
	}

	/**
	 * Returns the second shape of the contact.
	 */
	public getShape2(): Shape {
		return this._s2;
	}

	/**
	 * Returns whether the shapes are touching.
	 */
	public isTouching(): boolean {
		return this._touching;
	}

	/**
	 * Returns the contact manifold.
	 */
	public getManifold(): Manifold {
		return this._manifold;
	}

	/**
	 * Returns the contact constraint.
	 */
	public getContactConstraint(): ContactConstraint {
		return this._contactConstraint;
	}

	/**
	 * Returns the previous contact in the world.
	 *
	 * If the previous contact does not exist, `null` will be returned.
	 */
	public getPrev(): Contact {
		return this._prev;
	}

	/**
	 * Returns the next contact in the world.
	 *
	 * If the next contact does not exist, `null` will be returned.
	 */
	public getNext(): Contact {
		return this._next;
	}

}
